import java.awt.Point;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

/**
 * 
 */

/**
 * @author ES
 *
 */
public class State {

	public enum HeuristicType {MANHATTAN, MISPLACED, THEOTHERONE, EUCLIDEAN}
	public static HeuristicType hType = HeuristicType.MANHATTAN;
	private State parent; 
	//length of the path from this state to the start state
	private int distanceFromStart;
	//heuristic estimate of the distance from this state to the goal state
	private double heuristics;
	
	private List<Element> elements = new LinkedList<Element>();
	private static Vector<Point> positions = new Vector<Point>();
	static{
		positions.add(new Point(1,1));
		positions.add(new Point(1,2));
		positions.add(new Point(1,3));
		positions.add(new Point(2,3));
		positions.add(new Point(3,3));
		positions.add(new Point(3,2));
		positions.add(new Point(3,1));
		positions.add(new Point(2,1));
		positions.add(new Point(2,2));	
	}
	
	/**
	 * 
	 */
	public State(State theParent) {
		setParent(theParent);
		if(theParent != null){
			setDistanceFromStart(parent.getDistanceFromStart() + 1);
			copyParentList();
		}
		else{
			setDistanceFromStart(0);
			generateList();
			shuffleGoal();
		}
		setHeuristics();
		//setElements(new LinkedList<String>());
	}
	
	private void copyParentList() {
		
		for (Element e : getParent().elements) {
			elements.add(new Element(e));
		}
		
	}

	public void generateList(){
		elements.add(new Element('1', new Point(1,1)));
		elements.add(new Element('2', new Point(1,2)));
		elements.add(new Element('3', new Point(1,3)));
		elements.add(new Element('4', new Point(2,3)));
		elements.add(new Element('5', new Point(3,3)));
		elements.add(new Element('6', new Point(3,2)));
		elements.add(new Element('7', new Point(3,1)));
		elements.add(new Element('8', new Point(2,1)));
		elements.add(new Element('B', new Point(2,2)));		
	}
	
	public void shuffle(){
		/*Vector<Point> tempList = new Vector<Point>();
		for (Point point : positions) {
			tempList.add(point);
		}
		
		for(int i = 0; i < elements.size(); i++){
			
			elements.get(i).setCurrentPosition()
		}*/
		//--HARDCODED
		elements.get(0).setCurrentPosition(new Point(2,1));
		elements.get(1).setCurrentPosition(new Point(1,1));
		elements.get(2).setCurrentPosition(new Point(1,3));
		elements.get(3).setCurrentPosition(new Point(2,3));
		elements.get(4).setCurrentPosition(new Point(3,3));
		elements.get(5).setCurrentPosition(new Point(2,2));
		elements.get(6).setCurrentPosition(new Point(3,1));
		elements.get(7).setCurrentPosition(new Point(1,2));
		elements.get(8).setCurrentPosition(new Point(3,2));
	}
	public void shuffleMed(){
		elements.get(0).setCurrentPosition(new Point(1,3));
		elements.get(1).setCurrentPosition(new Point(1,1));
		elements.get(2).setCurrentPosition(new Point(2,3));
		elements.get(3).setCurrentPosition(new Point(2,2));
		elements.get(4).setCurrentPosition(new Point(3,3));
		elements.get(5).setCurrentPosition(new Point(3,2));
		elements.get(6).setCurrentPosition(new Point(3,1));
		elements.get(7).setCurrentPosition(new Point(1,2));
		elements.get(8).setCurrentPosition(new Point(2,1));
	}
	public void shuffleHard(){
		elements.get(0).setCurrentPosition(new Point(1,3));
		elements.get(1).setCurrentPosition(new Point(1,1));
		elements.get(2).setCurrentPosition(new Point(2,3));
		elements.get(3).setCurrentPosition(new Point(2,1));
		elements.get(4).setCurrentPosition(new Point(3,3));
		elements.get(5).setCurrentPosition(new Point(2,2));
		elements.get(6).setCurrentPosition(new Point(3,2));
		elements.get(7).setCurrentPosition(new Point(1,2));
		elements.get(8).setCurrentPosition(new Point(3,1));
	}
	
	public void shuffleWorst(){
		elements.get(0).setCurrentPosition(new Point(3,3));
		elements.get(1).setCurrentPosition(new Point(3,2));
		elements.get(2).setCurrentPosition(new Point(3,1));
		elements.get(3).setCurrentPosition(new Point(2,1));
		elements.get(4).setCurrentPosition(new Point(1,1));
		elements.get(5).setCurrentPosition(new Point(1,2));
		elements.get(6).setCurrentPosition(new Point(1,3));
		elements.get(7).setCurrentPosition(new Point(2,3));
		elements.get(8).setCurrentPosition(new Point(2,2));
	}
	
	public void shuffleGoal(){
		elements.get(0).setCurrentPosition(new Point(1,1));
		elements.get(1).setCurrentPosition(new Point(1,2));
		elements.get(2).setCurrentPosition(new Point(1,3));
		elements.get(3).setCurrentPosition(new Point(2,3));
		elements.get(4).setCurrentPosition(new Point(3,3));
		elements.get(5).setCurrentPosition(new Point(3,2));
		elements.get(6).setCurrentPosition(new Point(3,1));
		elements.get(7).setCurrentPosition(new Point(2,1));
		elements.get(8).setCurrentPosition(new Point(2,2));
	}

	public void swapTile(Point tile1, Point tile2){
	
		Element ele1 = getElementAtPos(tile1.x, tile1.y),
		ele2 = getElementAtPos(tile2.x, tile2.y);
		
		ele1.setCurrentPosition(tile2);
		ele2.setCurrentPosition(tile1);
		
		setHeuristics();
	}


	public double heuristicWeight()
	{
		return (distanceFromStart + heuristics);
	}
	
	
	/**
	 * *********************************** GETTERS AND SETTERS *************************************
	 */
	
	public State getParent() {
		return parent;
	}

	public void setParent(State parent) {
		this.parent = parent;
	}

	public int getDistanceFromStart() {
		return distanceFromStart;
	}

	public void setDistanceFromStart(int distanceFromStart) {
		this.distanceFromStart = distanceFromStart;
	}


	public double getHeuristics() {
		return heuristics;
	}



	public void setHeuristics() {
		
		switch(hType){
		case MANHATTAN:
			heuristics = getManhattanDistance();
			break;
		case MISPLACED:
			heuristics = getMisplacedTiles();
			break;
		case THEOTHERONE:
			heuristics = 2;
			break;
		case EUCLIDEAN:
			heuristics = getEuclideanDistance();//getEuclideanDistance();
			break;
		}
	}



	private double getEuclideanDistance() {
		double sum = 0;
		for (Element e : elements) {
			if(e.getID() != 'B')
				sum += e.getEuclidDistance();
		}
		return sum;
	}

	private int getMisplacedTiles() {
		int sum = 0;
		for (Element e : elements) {
			if(e.isWrongPosition() && e.getID() != 'B')
				sum++;
		}
		return sum;
	}

	private int getManhattanDistance() {
		int sum = 0;
		for (Element e : elements) {
			if(e.getID() != 'B')
				sum += e.getManhattanDistance();
		}
		return sum;
	}

	public List<Element> getElements() {
		return elements;
	}

	public void setElements(List<Element> elements) {
		this.elements = elements;
	}
	
	public Element getElementAtPos(int x, int y){
		for (Element e : elements) {
			if(e.getCurrentPosition().x == x && e.getCurrentPosition().y == y){
				return e;
			}
		}
		return null;
	}
	public boolean equals(State state){
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				if(this.getElementAtPos(i+1, j+1).getID() != state.getElementAtPos(i+1, j+1).getID()) 
					return false;
			}
		}
		return true;
	}
	
	public boolean equals(Object obj) {
		if (obj instanceof State) {
			return equals((State) obj);
		}
		return false;
	}

	public boolean isAtGoalState(){
		for (Element e : elements) {
			if(e.isWrongPosition()) return false;
		}
		return true;
	}
	
	public LinkedList<State> getPossibleMoves(){
		LinkedList<State> moves = new LinkedList<State>();
		
		
		LinkedList<Point> directions =  getBlankDirections();
		for (Point direction : directions) {
			State state = new State(this);
			state.swapTile(findBlank().getCurrentPosition(), direction);
			moves.add(state);
		}
		
		
		return moves;
	}
	
	public LinkedList<Point> getBlankDirections(){
		LinkedList<Point> directions = new LinkedList<Point>();
		Element blank = findBlank();
		Point blankPos = blank.getCurrentPosition();
		
		Point newPoint;
		
		newPoint = new Point(blankPos.x -1, blankPos.y);
		if(newPoint.x >= 1 )
			directions.add(newPoint); //-- Up
		
		newPoint = new Point(blankPos.x +1, blankPos.y);
		if(newPoint.x <= 3 )
			directions.add(newPoint); //-- Down
		
		newPoint = new Point(blankPos.x , blankPos.y - 1);
		if(newPoint.y >= 1 )
			directions.add(newPoint); //-- Left
		
		newPoint = new Point(blankPos.x , blankPos.y + 1);
		if(newPoint.y <= 3 )
			directions.add(newPoint); //-- Right
		
		return directions;
	}
	
	public Element findBlank(){
		for (Element e : elements) {
			if(e.getID() == 'B') return e;
		}
		return null;
	}


	 /** *********************************** GETTERS AND SETTERS ************************************ */
	public String printSequence(String seq){
		
		if(parent != null){
			seq += parent.printSequence(seq);
		}
		seq += toString() + "\n";
		return seq;
	}
	public String toString(){
		String toPrint = "";
		
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 3; j++) {
				toPrint += getElementAtPos(i+1, j+1).getID();
				toPrint += " ";
			}
			toPrint += "\n";
		}
		toPrint += "f(n) = " + heuristicWeight();
		toPrint += ", h(n) = " + heuristics;
		toPrint += ", g(n) = " + distanceFromStart + "\n";
		return toPrint;
	}

}
